/*
 *  linux/kernel/blk_drv/ramdisk.c
 *
 *  Written by Theodore Ts'o, 12/2/91
 */

#include <string.h>

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <asm/system.h>
#include <asm/segment.h>
#include <asm/memory.h>

#define MAJOR_NR 1
#include "blk.h"

// 以下两个全局变量在rd_init()中被重新赋值

char	*rd_start;  /* 虚拟盘在内存中的开始地址（绝对地址） */
int	rd_length = 0;	/* 虚拟盘所占内存大小（字节) */

// 虚拟盘当前请求项操作函数
// 实现：
// 该函数首先计算当前请求项中指定的起始扇区对应虚拟盘所处内存的起始位置 addr 和 要求的扇区数对应的字节长度值 len，然后根据请求项中的命令进行操作。
// 1、若是写命令WRITE，就把请求项所指缓冲区中的数据直接复制到内存位置 addr 处。
// 2、若是读操作则反之。
// 数据复制完成后即可直接调用 end_request(） 对本次请求项作结束处理。然后跳转到函数开始处再去处理下一个请求项。
// 若己没有请求项则退出。
void do_rd_request(void)
{
	int	len;
	char	*addr;

	INIT_REQUEST;
	addr = rd_start + (CURRENT->sector << 9);  /* 请求项处理的虚拟盘中起始扇区在物理内存中对应的地址 */
	len = CURRENT->nr_sectors << 9;  /* 将扇区数转换为字节长度 */
	// 如果子设备号不等于1 或 读取的扇区范围超出ramdisk区域的尾部  （ramdisk只有1个子设备？？）
	if ((MINOR(CURRENT->dev) != 1) || (addr+len > rd_start+rd_length)) {
		end_request(0);
		goto repeat;
	}
	if (CURRENT-> cmd == WRITE) {
		(void ) memcpy(addr,
			      CURRENT->buffer,
			      len);
	} else if (CURRENT->cmd == READ) {
		(void) memcpy(CURRENT->buffer, 
			      addr,
			      len);
	} else
		panic("unknown ramdisk-command");
	end_request(1);
	goto repeat;
}

/*
 * Returns amount of memory which needs to be reserved.
 */

// 虚拟盘初始化函数
// 该函数首先设置虚拟盘设备的请求项处理函数指针指向 do_rd_request()，
// 然后确定虚拟盘在物理内存中的起始地址、占用字节长度值。并对整个虚拟盘区清零。最后返回盘区长度。
long rd_init(long mem_start, int length)
{
	int	i;
	char	*cp;  // current pointer

	blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
	rd_start = (char *) mem_start;
	rd_length = length;
	cp = rd_start;
	// 对整个虚拟盘区清零
	for (i=0; i < length; i++)
		*cp++ = '\0';
	return(length);
}

/*
 * If the root device is the ram disk, try to load it.
 * In order to do this, the root device is originally set to the
 * floppy, and we later change it to be ram disk.
 */
// 把根文件系统加载到虚拟盘中
void rd_load(void)
{
	struct buffer_head *bh;
	struct super_block	s;
	int		block = 256;	/* Start at block 256 表示根文件系统映像文件被存储于 boot 盘第 256 磁盘块开始处 */
	int		i = 1;
	int		nblocks;
	char		*cp;		/* Move pointer ：当前指针 */
	
	// 首先检查虚拟盘的有效性和完整性。如果 ramdisk 的长度为零，则退出。否则显示 randisk 的大小以及内存起始位置。
	if (!rd_length)
		return;
	printk("Ram disk: %d bytes, starting at 0x%x\n", rd_length,
		(int) rd_start);
	// 如果此时根文件设备不是软盘设备，则也退出。
	if (MAJOR(ROOT_DEV) != 2)
		return;

	// 读根文件系统的基本参数。即读软盘块 256+1、256 和256+2。
	// 这里 block+1 是指磁盘上的超级块。
	// breada(）用于读取指定的数据块，并标出还需要读的块，然后返回含有数据块的缓冲区指针。
	// 如果返回 NULL，则表示数据块不可读（fs/buffer.c，322）。
	bh = breada(ROOT_DEV,block+1,block,block+2,-1);
	if (!bh) {
		printk("Disk error while looking for ramdisk!\n");
		return;
	}
	// 然后把缓冲区中的磁盘超级块（d_super_block 是磁盘超级块结构）复制到s变量中，并释放该磁盘块对应的缓冲区。
	*((struct d_super_block *) &s) = *((struct d_super_block *) bh->b_data);
	brelse(bh);
	// 接着我们开始对超级块的有效性进行判断。如果超级块中文件系统魔数不对，则说明加载的数据块不是 MINIX 文件系统，于是退出。
	// 有关 MINIX 超级块的结构请参见文件系统一章内容。
	if (s.s_magic != SUPER_MAGIC)
		/* No ram disk image present, assume normal floppy boot */
		return;
	
	// 然后我们试图把整个根文件系统读入到内存虚拟盘区中。
	// 对于一个文件系统来说，其超级块结构的 s_nzones 字段中保存着总逻辑块数（或称为区段数）。
	// 一个逻辑块中含有的数据块数则由字段 s_log_zone_size 指定。
	// 因此文件系统中的数据块总数 nblocks 就等于（逻辑块数 * 2^(每区段块数的次方)），即 nblocks = (s_ nzones * 2^s_log_zone_size)。
	nblocks = s.s_nzones << s.s_log_zone_size;
	// 如果遇到文件系统中数据块总数大于内存虚拟盘所能容纳的块数的情况，则不能执行加载操作，而只能显示出错信息并返回。
	if (nblocks > (rd_length >> BLOCK_SIZE_BITS)) {
		printk("Ram disk image too big!  (%d blocks, %d avail)\n", 
			nblocks, rd_length >> BLOCK_SIZE_BITS);
		return;
	}

	// 否则若虚拟盘能容纳得下文件系统总数据块数，则我们显示加载数据块信息，并让 cp 指向内存虚拟盘起始处，
	printk("Loading %d bytes into ram disk... 0000k", 
		nblocks << BLOCK_SIZE_BITS);
	cp = rd_start;
	// 然后开始执行循环操作将磁盘上根文件系统映像文件加载到虚拟盘上。
	while (nblocks) {
		// 在操作过程中，如果一次需要加载的盘块数大于2块，我们就是用超前预读函数 breada()， 
		// 否则就使用 bread()函数进行单块读取。
		if (nblocks > 2) 
			bh = breada(ROOT_DEV, block, block+1, block+2, -1);
		else
			bh = bread(ROOT_DEV, block);
		// 若在读盘过程中出现I/O 操作错误，就只能放弃加载过程返回。
		if (!bh) {
			printk("I/O error on block %d, aborting load\n", 
				block);
			return;
		}
		// 所读取的磁盘块会使用 memcpy(） 函数从高速缓冲区中复制到内存虚拟盘相应位置处，同时显示己加载的块数。显示字符串中的八进制数’\010’表示显示一个制表符。
		(void) memcpy(cp, bh->b_data, BLOCK_SIZE);
		brelse(bh);
		printk("\010\010\010\010\010%4dk",i);
		cp += BLOCK_SIZE;
		block++;
		nblocks--;
		i++;
	}
	printk("\010\010\010\010\010done \n");
	ROOT_DEV=0x0101;
}
